{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c024bfa4-1a7a-4751-b5a1-827225a3478b",
   "metadata": {
    "id": "c024bfa4-1a7a-4751-b5a1-827225a3478b"
   },
   "source": [
    "<font size=\"1\">\n",
    "Code based on \"Build a Large Language Model From Scratch\": <a href=\"https://www.manning.com/books/build-a-large-language-model-from-scratch\">https://www.manning.com/books/build-a-large-language-model-from-scratch</a> by <a href=\"https://sebastianraschka.com\">Sebastian Raschka</a><br>\n",
    "Code repository: <a href=\"https://github.com/rasbt/LLMs-from-scratch\">https://github.com/rasbt/LLMs-from-scratch</a>\n",
    "</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bfabadb8-5935-45ff-b39c-db7a29012129",
   "metadata": {
    "id": "bfabadb8-5935-45ff-b39c-db7a29012129"
   },
   "source": [
    "# Finetuning GPT for Text Classification"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "5b7e01c2-1c84-4f2a-bb51-2e0b74abda90",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "5b7e01c2-1c84-4f2a-bb51-2e0b74abda90",
    "outputId": "9495f150-9d79-4910-d6e7-6c0d9aae4a41"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "matplotlib version: 3.8.4\n",
      "numpy version: 1.26.4\n",
      "tiktoken version: 0.6.0\n",
      "torch version: 2.3.0\n",
      "tensorflow version: 2.16.1\n",
      "pandas version: 2.2.2\n"
     ]
    }
   ],
   "source": [
    "from importlib.metadata import version\n",
    "\n",
    "pkgs = [\"matplotlib\",\n",
    "        \"numpy\",\n",
    "        \"tiktoken\",\n",
    "        \"torch\",\n",
    "        \"tensorflow\", # For OpenAI's pretrained weights\n",
    "        \"pandas\"      # Dataset loading\n",
    "       ]\n",
    "for p in pkgs:\n",
    "    print(f\"{p} version: {version(p)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c7017a2-32aa-4002-a2f3-12aac293ccdf",
   "metadata": {
    "id": "8c7017a2-32aa-4002-a2f3-12aac293ccdf"
   },
   "source": [
    "## 1) Preparing the dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9fbd459f-63fa-4d8c-8499-e23103156c7d",
   "metadata": {
    "id": "9fbd459f-63fa-4d8c-8499-e23103156c7d"
   },
   "source": [
    "- We use a dataset conisting of SPAM (label 1) and non-SPAM (label 0) text messages to finetune the LLM to classify them"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "da0ed4da-ac31-4e4d-8bdd-2153be4656a4",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 423
    },
    "id": "da0ed4da-ac31-4e4d-8bdd-2153be4656a4",
    "outputId": "a16c5cde-d341-4887-a93f-baa9bec542ab"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Label</th>\n",
       "      <th>Text</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>Leave it wif me lar... Ü wan to carry meh so h...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>Enjoy the jamster videosound gold club with yo...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>1</td>\n",
       "      <td>You are being contacted by our dating service ...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1</td>\n",
       "      <td>SMS SERVICES For your inclusive text credits p...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>0</td>\n",
       "      <td>do u think that any girl will propose u today ...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1040</th>\n",
       "      <td>1</td>\n",
       "      <td>You are being contacted by our Dating Service ...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1041</th>\n",
       "      <td>0</td>\n",
       "      <td>Say this slowly.? GOD,I LOVE YOU &amp;amp; I NEED ...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1042</th>\n",
       "      <td>1</td>\n",
       "      <td>You can donate £2.50 to UNICEF's Asian Tsunami...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1043</th>\n",
       "      <td>0</td>\n",
       "      <td>Hi good mornin.. Thanku wish u d same..</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1044</th>\n",
       "      <td>1</td>\n",
       "      <td>Urgent Ur £500 guaranteed award is still uncla...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>1045 rows × 2 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "      Label                                               Text\n",
       "0         0  Leave it wif me lar... Ü wan to carry meh so h...\n",
       "1         1  Enjoy the jamster videosound gold club with yo...\n",
       "2         1  You are being contacted by our dating service ...\n",
       "3         1  SMS SERVICES For your inclusive text credits p...\n",
       "4         0  do u think that any girl will propose u today ...\n",
       "...     ...                                                ...\n",
       "1040      1  You are being contacted by our Dating Service ...\n",
       "1041      0  Say this slowly.? GOD,I LOVE YOU &amp; I NEED ...\n",
       "1042      1  You can donate £2.50 to UNICEF's Asian Tsunami...\n",
       "1043      0            Hi good mornin.. Thanku wish u d same..\n",
       "1044      1  Urgent Ur £500 guaranteed award is still uncla...\n",
       "\n",
       "[1045 rows x 2 columns]"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "df = pd.read_csv(\"train.csv\")\n",
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c188a951",
   "metadata": {},
   "outputs": [],
   "source": [
    "from spam_classifier_utils import SpamDataset\n",
    "import tiktoken\n",
    "\n",
    "tokenizer = tiktoken.get_encoding(\"gpt2\")\n",
    "train_dataset = SpamDataset(\"train.csv\", max_length=None, tokenizer=tokenizer)\n",
    "val_dataset = SpamDataset(\"validation.csv\", max_length=train_dataset.max_length, tokenizer=tokenizer)\n",
    "test_dataset = SpamDataset(\"test.csv\", max_length=train_dataset.max_length, tokenizer=tokenizer)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "8681adc0-6f02-4e75-b01a-a6ab75d05542",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "8681adc0-6f02-4e75-b01a-a6ab75d05542",
    "outputId": "3266c410-4fdb-4a8c-a142-7f707e2525ab"
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "num_workers = 0\n",
    "batch_size = 8\n",
    "\n",
    "torch.manual_seed(123)\n",
    "\n",
    "train_loader = DataLoader(\n",
    "    dataset=train_dataset,\n",
    "    batch_size=batch_size,\n",
    "    shuffle=True,\n",
    "    num_workers=num_workers,\n",
    "    drop_last=True,\n",
    ")\n",
    "\n",
    "val_loader = DataLoader(\n",
    "    dataset=val_dataset,\n",
    "    batch_size=batch_size,\n",
    "    num_workers=num_workers,\n",
    "    drop_last=False,\n",
    ")\n",
    "\n",
    "test_loader = DataLoader(\n",
    "    dataset=test_dataset,\n",
    "    batch_size=batch_size,\n",
    "    num_workers=num_workers,\n",
    "    drop_last=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1c4f61a-5f5d-4b3b-97cf-151b617d1d6c",
   "metadata": {
    "id": "d1c4f61a-5f5d-4b3b-97cf-151b617d1d6c"
   },
   "source": [
    "## 2) Initializing a model with pretrained weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "2992d779-f9fb-4812-a117-553eb790a5a9",
   "metadata": {
    "id": "2992d779-f9fb-4812-a117-553eb790a5a9"
   },
   "outputs": [],
   "source": [
    "CHOOSE_MODEL = \"gpt2-small (124M)\"\n",
    "INPUT_PROMPT = \"Every effort moves\"\n",
    "\n",
    "BASE_CONFIG = {\n",
    "    \"vocab_size\": 50257,     # Vocabulary size\n",
    "    \"context_length\": 1024,  # Context length\n",
    "    \"drop_rate\": 0.0,        # Dropout rate\n",
    "    \"qkv_bias\": True         # Query-key-value bias\n",
    "}\n",
    "\n",
    "model_configs = {\n",
    "    \"gpt2-small (124M)\": {\"emb_dim\": 768, \"n_layers\": 12, \"n_heads\": 12},\n",
    "    \"gpt2-medium (355M)\": {\"emb_dim\": 1024, \"n_layers\": 24, \"n_heads\": 16},\n",
    "    \"gpt2-large (774M)\": {\"emb_dim\": 1280, \"n_layers\": 36, \"n_heads\": 20},\n",
    "    \"gpt2-xl (1558M)\": {\"emb_dim\": 1600, \"n_layers\": 48, \"n_heads\": 25},\n",
    "}\n",
    "\n",
    "BASE_CONFIG.update(model_configs[CHOOSE_MODEL])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "022a649a-44f5-466c-8a8e-326c063384f5",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "022a649a-44f5-466c-8a8e-326c063384f5",
    "outputId": "7091e401-8442-4f47-a1d9-ecb42a1ef930"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2024-05-01 12:56:06.125211: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n",
      "To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n",
      "2024-05-01 12:56:06.881160: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "File already exists and is up-to-date: gpt2/124M/checkpoint\n",
      "File already exists and is up-to-date: gpt2/124M/encoder.json\n",
      "File already exists and is up-to-date: gpt2/124M/hparams.json\n",
      "File already exists and is up-to-date: gpt2/124M/model.ckpt.data-00000-of-00001\n",
      "File already exists and is up-to-date: gpt2/124M/model.ckpt.index\n",
      "File already exists and is up-to-date: gpt2/124M/model.ckpt.meta\n",
      "File already exists and is up-to-date: gpt2/124M/vocab.bpe\n"
     ]
    }
   ],
   "source": [
    "# imports from local files\n",
    "from gpt_download import download_and_load_gpt2\n",
    "from spam_classifier_utils import GPTModel, load_weights_into_gpt\n",
    "\n",
    "model_size = CHOOSE_MODEL.split(\" \")[-1].lstrip(\"(\").rstrip(\")\")\n",
    "settings, params = download_and_load_gpt2(model_size=model_size, models_dir=\"gpt2\")\n",
    "\n",
    "model = GPTModel(BASE_CONFIG)\n",
    "load_weights_into_gpt(model, params)\n",
    "model.eval();"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ab8e056c-abe0-415f-b34d-df686204259e",
   "metadata": {},
   "source": [
    "- To ensure that the model was loaded corrected, let's double-check that it generates coherent text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "fe4af171-5dce-4f6e-9b63-1e4e16e8b94c",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "fe4af171-5dce-4f6e-9b63-1e4e16e8b94c",
    "outputId": "8ff3ec54-1dc3-4930-9be6-8eeaf560f8d4"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Output text: Every effort moves you forward.\n",
      "\n",
      "The first step is to understand the importance of your work\n"
     ]
    }
   ],
   "source": [
    "from spam_classifier_utils import generate_text_simple\n",
    "\n",
    "start_context = \"Every effort moves you\"\n",
    "\n",
    "tokenizer = tiktoken.get_encoding(\"gpt2\")\n",
    "encoded = tokenizer.encode(start_context)\n",
    "encoded_tensor = torch.tensor(encoded).unsqueeze(0)\n",
    "\n",
    "out = generate_text_simple(\n",
    "    model=model,\n",
    "    idx=encoded_tensor,\n",
    "    max_new_tokens=15,\n",
    "    context_size=BASE_CONFIG[\"context_length\"]\n",
    ")\n",
    "decoded_text = tokenizer.decode(out.squeeze(0).tolist())\n",
    "\n",
    "print(\"Output text:\", decoded_text)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c9ae440-32f9-412f-96cf-fd52cc3e2522",
   "metadata": {
    "id": "4c9ae440-32f9-412f-96cf-fd52cc3e2522"
   },
   "source": [
    "## 3) Adding a classification head"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "217bac05-78df-4412-bd80-612f8061c01d",
   "metadata": {},
   "source": [
    "- In this section, we are modifying the pretrained LLM to make it ready for classification finetuning\n",
    "- Let's take a look at the model architecture first"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "b23aff91-6bd0-48da-88f6-353657e6c981",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "1d8f7a01-b7c0-48d4-b1e7-8c12cc7ad932",
    "outputId": "b6a5b9b5-a92f-498f-d7cb-b58dd99e4497"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GPTModel(\n",
      "  (tok_emb): Embedding(50257, 768)\n",
      "  (pos_emb): Embedding(1024, 768)\n",
      "  (drop_emb): Dropout(p=0.0, inplace=False)\n",
      "  (trf_blocks): Sequential(\n",
      "    (0): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (1): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (2): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (3): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (4): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (5): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (6): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (7): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (8): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (9): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (10): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "    (11): TransformerBlock(\n",
      "      (att): MultiHeadAttention(\n",
      "        (W_query): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_key): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (W_value): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (out_proj): Linear(in_features=768, out_features=768, bias=True)\n",
      "        (dropout): Dropout(p=0.0, inplace=False)\n",
      "      )\n",
      "      (ff): FeedForward(\n",
      "        (layers): Sequential(\n",
      "          (0): Linear(in_features=768, out_features=3072, bias=True)\n",
      "          (1): GELU()\n",
      "          (2): Linear(in_features=3072, out_features=768, bias=True)\n",
      "        )\n",
      "      )\n",
      "      (norm1): LayerNorm()\n",
      "      (norm2): LayerNorm()\n",
      "      (drop_resid): Dropout(p=0.0, inplace=False)\n",
      "    )\n",
      "  )\n",
      "  (final_norm): LayerNorm()\n",
      "  (out_head): Linear(in_features=768, out_features=50257, bias=False)\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "print(model)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f640a76-dd00-4769-9bc8-1aed0cec330d",
   "metadata": {},
   "source": [
    "- The goal is to replace and finetune the output layer\n",
    "- To achieve this, we first freeze the model, meaning that we make all layers non-trainable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "fkMWFl-0etea",
   "metadata": {
    "id": "fkMWFl-0etea"
   },
   "outputs": [],
   "source": [
    "for param in model.parameters():\n",
    "    param.requires_grad = False"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72155f83-87d9-476a-a978-a15aa2d44147",
   "metadata": {},
   "source": [
    "- Then, we replace the output layer (`model.out_head`), which originally maps the layer inputs to 50,257 dimensions (the size of the vocabulary)\n",
    "- Since we finetune the model for binary classification (predicting 2 classes \"spam\" and \"ham\"), we can replace the output layer as shown below, which will be trainable by default"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "7e759fa0-0f69-41be-b576-17e5f20e04cb",
   "metadata": {},
   "outputs": [],
   "source": [
    "torch.manual_seed(123)\n",
    "\n",
    "num_classes = 2\n",
    "model.out_head = torch.nn.Linear(in_features=768, out_features=num_classes)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30be5475-ae77-4f97-8f3e-dec462b1339f",
   "metadata": {},
   "source": [
    "- Technically, it's sufficient to only train the output layer\n",
    "- However, as I found in [experiments finetuning additional layers](https://magazine.sebastianraschka.com/p/finetuning-large-language-models) can noticeably improve the performance\n",
    "- So, we are also making the last transformer block and the final `LayerNorm` module connecting the last transformer block to the output layer trainable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "2aedc120-5ee3-48f6-92f2-ad9304ebcdc7",
   "metadata": {
    "id": "2aedc120-5ee3-48f6-92f2-ad9304ebcdc7"
   },
   "outputs": [],
   "source": [
    "for param in model.trf_blocks[-1].parameters():\n",
    "    param.requires_grad = True\n",
    "\n",
    "for param in model.final_norm.parameters():\n",
    "    param.requires_grad = True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32aa4aef-e1e9-491b-9adf-5aa973e59b8c",
   "metadata": {},
   "source": [
    "## 4) Calculating the initial classification accuracy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "2f1e9547-806c-41a9-8aba-3b2822baabe4",
   "metadata": {
    "id": "2f1e9547-806c-41a9-8aba-3b2822baabe4"
   },
   "outputs": [],
   "source": [
    "from spam_classifier_utils import calc_accuracy_loader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "f6f00e53-5beb-4e64-b147-f26fd481c6ff",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "f6f00e53-5beb-4e64-b147-f26fd481c6ff",
    "outputId": "49df8648-9e38-4314-854d-9faacd1b2e89"
   },
   "outputs": [],
   "source": [
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "model.to(device);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "64ce5b12-84cd-488c-8ea7-4cef5b2d947e",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "64ce5b12-84cd-488c-8ea7-4cef5b2d947e",
    "outputId": "239581b4-fd0f-4adf-e67b-364e0f0f96b7"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training accuracy: 45.00%\n",
      "Validation accuracy: 53.75%\n",
      "Test accuracy: 48.75%\n"
     ]
    }
   ],
   "source": [
    "torch.manual_seed(123)\n",
    "train_accuracy = calc_accuracy_loader(train_loader, model, device, num_batches=10)\n",
    "val_accuracy = calc_accuracy_loader(val_loader, model, device, num_batches=10)\n",
    "test_accuracy = calc_accuracy_loader(test_loader, model, device, num_batches=10)\n",
    "\n",
    "print(f\"Training accuracy: {train_accuracy*100:.2f}%\")\n",
    "print(f\"Validation accuracy: {val_accuracy*100:.2f}%\")\n",
    "print(f\"Test accuracy: {test_accuracy*100:.2f}%\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "456ae0fd-6261-42b4-ab6a-d24289953083",
   "metadata": {
    "id": "456ae0fd-6261-42b4-ab6a-d24289953083"
   },
   "source": [
    "## 5) Finetuning the model on supervised data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "Csbr60to50FL",
   "metadata": {
    "id": "Csbr60to50FL"
   },
   "outputs": [],
   "source": [
    "from spam_classifier_utils import calc_loss_batch, evaluate_model\n",
    "\n",
    "\n",
    "# Overall the same as `train_model_simple` in chapter 5\n",
    "def train_classifier_simple(model, train_loader, val_loader, optimizer, device, num_epochs,\n",
    "                            eval_freq, eval_iter, tokenizer, max_steps=None):\n",
    "    # Initialize lists to track losses and tokens seen\n",
    "    train_losses, val_losses, train_accs, val_accs = [], [], [], []\n",
    "    examples_seen, global_step = 0, -1\n",
    "\n",
    "    # Main training loop\n",
    "    for epoch in range(num_epochs):\n",
    "        model.train()  # Set model to training mode\n",
    "\n",
    "\n",
    "        ####################################################################\n",
    "        # EXERCISE\n",
    "        ####################################################################\n",
    "        for input_batch, target_batch in train_loader:\n",
    "            optimizer.zero_grad()  # Reset loss gradients from previous epoch\n",
    "            loss = calc_loss_batch(input_batch, target_batch, model, device)\n",
    "            # TODO: Implement the backward pass to calculate gradients\n",
    "            # TODO: Implement the step function of the optimizer to update model weights\n",
    "\n",
    "        ####################################################################\n",
    "\n",
    "\n",
    "            # Optional tracking and evaluation steps\n",
    "            examples_seen += input_batch.shape[0] # New: track examples instead of tokens\n",
    "            global_step += 1\n",
    "\n",
    "            if global_step % eval_freq == 0:\n",
    "                train_loss, val_loss = evaluate_model(\n",
    "                    model, train_loader, val_loader, device, eval_iter)\n",
    "                train_losses.append(train_loss)\n",
    "                val_losses.append(val_loss)\n",
    "                print(f\"Ep {epoch+1} (Step {global_step:06d}): \"\n",
    "                      f\"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}\")\n",
    "\n",
    "            if max_steps is not None and global_step > max_steps:\n",
    "                break\n",
    "\n",
    "        # Calculate accuracy after each epoch\n",
    "        train_accuracy = calc_accuracy_loader(train_loader, model, device, num_batches=eval_iter)\n",
    "        val_accuracy = calc_accuracy_loader(val_loader, model, device, num_batches=eval_iter)\n",
    "        print(f\"Training accuracy: {train_accuracy*100:.2f}% | \", end=\"\")\n",
    "        print(f\"Validation accuracy: {val_accuracy*100:.2f}%\")\n",
    "        train_accs.append(train_accuracy)\n",
    "        val_accs.append(val_accuracy)\n",
    "\n",
    "        if max_steps is not None and global_step > max_steps:\n",
    "            break\n",
    "\n",
    "    return train_losses, val_losses, train_accs, val_accs, examples_seen"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "X7kU3aAj7vTJ",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "X7kU3aAj7vTJ",
    "outputId": "504a033e-2bf8-41b5-a037-468309845513"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ep 1 (Step 000000): Train loss 2.744, Val loss 2.511\n",
      "Ep 1 (Step 000050): Train loss 0.602, Val loss 0.605\n",
      "Ep 1 (Step 000100): Train loss 0.484, Val loss 0.511\n",
      "Training accuracy: 82.50% | Validation accuracy: 92.50%\n",
      "Ep 2 (Step 000150): Train loss 0.490, Val loss 0.402\n",
      "Ep 2 (Step 000200): Train loss 0.502, Val loss 0.342\n",
      "Ep 2 (Step 000250): Train loss 0.322, Val loss 0.289\n",
      "Training accuracy: 82.50% | Validation accuracy: 87.50%\n",
      "Ep 3 (Step 000300): Train loss 0.499, Val loss 0.258\n",
      "Ep 3 (Step 000350): Train loss 0.331, Val loss 0.142\n",
      "Training accuracy: 85.00% | Validation accuracy: 95.00%\n",
      "Ep 4 (Step 000400): Train loss 0.239, Val loss 0.090\n",
      "Ep 4 (Step 000450): Train loss 0.352, Val loss 0.096\n",
      "Ep 4 (Step 000500): Train loss 0.106, Val loss 0.078\n",
      "Training accuracy: 100.00% | Validation accuracy: 97.50%\n",
      "Ep 5 (Step 000550): Train loss 0.067, Val loss 0.076\n",
      "Ep 5 (Step 000600): Train loss 0.127, Val loss 0.083\n",
      "Training accuracy: 100.00% | Validation accuracy: 97.50%\n",
      "Training completed in 0.34 minutes.\n",
      "GPU memory used: 0.76 GB\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "\n",
    "start_time = time.time()\n",
    "\n",
    "torch.manual_seed(123)\n",
    "\n",
    "optimizer = torch.optim.AdamW(model.parameters(), lr=5e-5, weight_decay=0.1)\n",
    "\n",
    "num_epochs = 5\n",
    "train_losses, val_losses, train_accs, val_accs, examples_seen = train_classifier_simple(\n",
    "    model, train_loader, val_loader, optimizer, device,\n",
    "    num_epochs=num_epochs, eval_freq=50, eval_iter=5,\n",
    "    tokenizer=tokenizer, max_steps=None\n",
    ")\n",
    "\n",
    "end_time = time.time()\n",
    "execution_time_minutes = (end_time - start_time) / 60\n",
    "print(f\"Training completed in {execution_time_minutes:.2f} minutes.\")\n",
    "\n",
    "\n",
    "if torch.cuda.is_available():\n",
    "    print(f\"GPU memory used: {torch.cuda.max_memory_allocated() / 1e9:.02f} GB\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b9f66eea",
   "metadata": {},
   "source": [
    "## 6) Evaluating the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "UHWaJFrjY0zW",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "UHWaJFrjY0zW",
    "outputId": "e111e6e6-b147-4159-eb9d-19d4e809ed34"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training accuracy: 96.63%\n",
      "Validation accuracy: 99.33%\n",
      "Test accuracy: 95.00%\n"
     ]
    }
   ],
   "source": [
    "train_accuracy = calc_accuracy_loader(train_loader, model, device)\n",
    "val_accuracy = calc_accuracy_loader(val_loader, model, device)\n",
    "test_accuracy = calc_accuracy_loader(test_loader, model, device)\n",
    "\n",
    "print(f\"Training accuracy: {train_accuracy*100:.2f}%\")\n",
    "print(f\"Validation accuracy: {val_accuracy*100:.2f}%\")\n",
    "print(f\"Test accuracy: {test_accuracy*100:.2f}%\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "OIqRt466DiGk",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 307
    },
    "id": "OIqRt466DiGk",
    "outputId": "b16987cf-0001-4652-ddaf-02f7cffc34db"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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HHJycigvLzfWmTBhAjNmzDB+nj17Nn/88QfHjx9n9+7dPPzww6Snp/P444+b4xCgw2DDa9om+tR2KNubWURFtc488QghhGhVzPp41rx58wCMPfTOW7hwIRMnTgQMw7yp1Rd+TxQUFDBp0iRycnJwc3OjV69ebN261eS5uWbVobad+sQWAl2taeOo5UxpJSmniow9wYUQQoiGMmuirkuH840bN5p8/vDDD/nwww+bKKIG8AwFew84dxbVqd30CXLj95QcEk4USKIWQghxwyyiM1mLplZD+9p2lbRN9KqdSUuepxZCCNEYJFE3hvPt1McvtFMnpheg11vEI+pCCCFaMEnUjeH889Qnd9KtjRo7GyuKyqs5errUvHEJIYRo8SRRNwb39uAaAPoabE7uIDLAFTBMeymEEJZu8ODBTJ061fg5KCiIuXPnXvM7KpWK5cuX3/C+G2s71zJr1iwiIiKadB9NSRJ1Yzl/VX1840XjfsvAJ0KIpjNq1CiGDx9+xXV//vknKpWKffv21Xu7CQkJPPHEEzcanomrJcvs7GxGjBjRqPtqbcza67tViRgPPuHQaSh9zhpGP5MpL4UQTemxxx7j/vvv5+TJk5eN6rhw4UJ69+5NWFhYvbfbtm3bxgrxuixtJkRLJFfUjSUwCvpOAvcORAa4oVZBZn45OUUV5o5MCNFK3X333bRt25ZFixaZlJeWlvLDDz/w2GOPcfbsWcaNG4efnx/29vb06NGDxYsXX3O7l976PnLkCAMHDsTW1pZu3boRHx9/2XdeeuklOnfujL29PR06dOC1116juroaMEw3+cYbb7B3715UKhUqlcoY86W3vpOTk7n99tuxs7PDw8ODJ554gtLSC/19Jk6cyOjRo3nvvffw8fHBw8OD2NhY477qQq/XM3v2bNq1a4dWqyUiIsJk2uWqqiomT56Mj48Ptra2BAYGEhcXBxgeK541axYBAQFotVp8fX2ZMmVKnffdEHJF3QQctdZ083Um5VQxien53B3ma+6QhBANVVVW/+9YacGq9s+rrgZ0laBSg43d9bercajzbqytrZkwYQKLFi3ilVdeMc7l/MMPP6DT6Rg3bhylpaX06tWLl156CWdnZ1auXMkjjzxCx44d6du373X3odfrue+++/Dy8mLHjh0UFRWZtGef5+TkxKJFi/D19SU5OZlJkybh5OTEiy++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",
      "text/plain": [
       "<Figure size 500x300 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from spam_classifier_utils import plot_values\n",
    "\n",
    "epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))\n",
    "examples_seen_tensor = torch.linspace(0, examples_seen, len(train_losses))\n",
    "\n",
    "plot_values(epochs_tensor, examples_seen_tensor, train_losses, val_losses)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "yz8BIsaF0TUo",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 307
    },
    "id": "yz8BIsaF0TUo",
    "outputId": "3a7ed967-1f2a-4c6d-f4a3-0cc8cc9d6c5f"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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xowZg8FgeICavOPbb7/FxsaG6OhovvrqK8A4g9usWbM4fPgw3377LevXr2fMmDGmz8TExNChQwfq1avH9u3b2bp1K2FhYej1ep5//nn0er3ZF5tLly6xcuVKXn755XzFJooX45hg43S84S39aFjZTduARJGRx9EF4Fa2nnqT1ty/YAE7MjUUB5uC+V84depUnnzySdP7cuXKERgYaHr/7rvvsmzZMlasWMGwYcPuepz+/fvTq1cvAN5//31mzZrFrl276NSpU57ls7Oz+eqrr6hRowYAw4YNY+rUqab9n3/+OePGjaN79+4AfPHFF6xateqe11KpUiWzRD58+HDWrFnDTz/9RIsWLUhNTeWzzz7jiy++IDw8HIAaNWrQunVrABYuXMjly5fZvXs35cqVA6BmzZr3PGde/P39mT59utm221OsgvFJwnvvvcfgwYP58ssvAZg+fTrNmjUzvQeoX7++6efevXszf/58nn/+eQB++OEHqlatanYXLkqeWVEniLt2k4qudrzVsbbW4YgiJElYANCsWTOz92lpaUyePJmVK1eSkJBATk4Ot27duu+dcMOGDU0/Ozo64uLics8VrBwcHEwJGIzTJN4un5ycTFJSEi1atDDtt7S0pGnTpve8K9Xr9bz//vv89NNPxMfHk5WVRWZmpqkD2dGjR8nMzKRDhw55fj4mJobGjRubEvDDatq0aa5t69atIzIykmPHjpGSkkJOTg4ZGRncvHkTBwcHYmJiTAk2L4MGDaJ58+bEx8dTqVIlFixYQP/+/aWjWwl2LDGFrzefBmDKM/VxspU/y2WJ/N8uAPbWlhyZGnr/goVw3oLi6Oho9n706NGsXbuWjz/+mJo1a2Jvb89zzz1HVlbWPY9jbW0+qYBOp7tnwsyr/KM+Zv/oo4/47LPPmDlzpqn9deTIkabYb0/LeDf3229hYZErxrxW0/p3nZ49e5ann36aIUOGMG3aNMqVK8fWrVsZOHAgWVlZODg43PfcjRs3JjAwkO+++46OHTty+PBhVq5cec/PiOLLYFC8szSWHIMitL4XHeuXzY6LZZm0CRcAnU6Hg41Vkb8K8+4nOjqa/v370717dwICAvD29ubs2bOFdr68uLq64uXlxe7du03b9Ho9+/btu+fnoqOj6dq1Ky+99BKBgYFUr16dv/76y7Tf398fe3v7XLOv3dawYUNi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",
      "text/plain": [
       "<Figure size 500x300 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "epochs_tensor = torch.linspace(0, num_epochs, len(train_accs))\n",
    "examples_seen_tensor = torch.linspace(0, examples_seen, len(train_accs))\n",
    "\n",
    "plot_values(epochs_tensor, examples_seen_tensor, train_accs, val_accs, label=\"accuracy\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a74d9ad7-3ec1-450e-8c9f-4fc46d3d5bb0",
   "metadata": {},
   "source": [
    "## 7) Using the finetuned LLM as a SPAM classifier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "aHdn6xvL-IW5",
   "metadata": {
    "id": "aHdn6xvL-IW5"
   },
   "outputs": [],
   "source": [
    "from spam_classifier_utils import classify_review"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "apU_pf51AWSV",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "apU_pf51AWSV",
    "outputId": "d0fde0a5-e7a3-4dbe-d9c5-0567dbab7e62"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Positive\n"
     ]
    }
   ],
   "source": [
    "text_1 = (\n",
    "    \"Congratulations! You have WON a $500 Amazon gift card\"\n",
    "    \" in our exclusive lucky draw!\"\n",
    ")\n",
    "\n",
    "print(classify_review(text_1, model, tokenizer, device, pad_length=train_dataset._longest_encoded_length))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "1g5VTOo_Ajs5",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "1g5VTOo_Ajs5",
    "outputId": "659b08eb-b6a9-4a8a-9af7-d94c757e93c2"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Negative\n"
     ]
    }
   ],
   "source": [
    "text_2 = (\n",
    "    \"Reminder: School Parent-Teacher meeting tonight at 7 PM in the main \"\n",
    "    \"auditorium. We look forward to seeing you there and discussing your \"\n",
    "    \"child's progress.\"\n",
    ")\n",
    "\n",
    "print(classify_review(text_2, model, tokenizer, device))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "mYnX-gI1CfQY",
   "metadata": {
    "id": "mYnX-gI1CfQY"
   },
   "outputs": [],
   "source": [
    "torch.save(model.state_dict(), \"review_classifier.pth\")"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "gpuType": "V100",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
